﻿/*
    本例改编自gtk example bloatpad.
    展示了，
        .默认资源gtk/menus.ui和gtk/help-overlay.ui
        .app accelarators管理
        .layout中grid, box
        .toolbar, toggleToolButton,
        .dynamic menu
        .file icon, themed icon, bytes icon, emblemed icon
        .textViewText, textBuffer
        .fullscreen
        .aboutDialog
        .dialog and its response processing.
*/
#include <stdlib.h>
#include <gtk/gtk.h>

typedef struct 
{
    GtkApplication parent;

    /* data */
    GMenu* m_timeMenu;
    guint m_timeout;
} BloatPad;

typedef GtkApplicationClass BloatPadClass;

G_DEFINE_TYPE(BloatPad, bloat_pad, GTK_TYPE_APPLICATION)

static void text_buffer_changed_cb(GtkTextBuffer *buf, gpointer user_data)
{
    GtkWindow *window = user_data;
    BloatPad *bp = (BloatPad*) gtk_window_get_application(window);

    gint old_n;
    gint n;
    n = gtk_text_buffer_get_char_count(buf);

    g_simple_action_set_enabled(G_SIMPLE_ACTION(g_action_map_lookup_action(G_ACTION_MAP(window), "clear")), n>0);

    if(n > 0)
    {
        GSimpleAction *spellcheck = g_simple_action_new("spell-check", NULL);
        g_action_map_add_action(G_ACTION_MAP(window), G_ACTION(spellcheck));
    }
    else
    {
        g_action_map_remove_action(G_ACTION_MAP(window), "spell-check");
    }

    old_n = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(buf), "line-count"));
    n = gtk_text_buffer_get_line_count(buf);
    g_object_set_data(G_OBJECT(buf), "line-count", GINT_TO_POINTER(n));

    if(old_n < 3 && n == 3)
    {
        GNotification *noti = g_notification_new("Three lines of text.");
        g_notification_set_body(noti, "Keep up the good work");
        g_notification_add_button(noti, "Start over", "app.clear-all");
        g_application_send_notification(G_APPLICATION(bp), "three-lines", noti);
        g_object_unref(noti);
    }
}

static GtkClipboard* get_clipboard(GtkWidget* widget)
{
    return gtk_widget_get_clipboard(widget, gdk_atom_intern_static_string("CLIPBOARD"));
}

//win.copy
static void window_copy_activated(GSimpleAction *action, GVariant *param, gpointer user_data)
{
    GtkWindow *window = GTK_WINDOW(user_data);
    GtkTextView *text = (GtkTextView*) g_object_get_data(G_OBJECT(window), "bloatpad-text");
    gtk_text_buffer_copy_clipboard(gtk_text_view_get_buffer(text)
                                , get_clipboard(GTK_WIDGET(text)));

}

//win.paste
static void window_paste_activated(GSimpleAction *action, GVariant *param, gpointer user_data)
{
    GtkWindow *window = GTK_WINDOW(user_data);
    GtkTextView *text = (GtkTextView*) g_object_get_data(G_OBJECT(window), "bloatpad-text");

    gtk_text_buffer_paste_clipboard(gtk_text_view_get_buffer(text)
                                , get_clipboard(GTK_WIDGET(text))
                                , NULL
                                , TRUE);

}

//win.busy and win.fullscreen
static void toggle_activated(GSimpleAction *action, GVariant *param, gpointer user_data)
{
    GVariant *state;

    state = g_action_get_state(G_ACTION(action));

    g_action_change_state(G_ACTION(action), g_variant_new_boolean (!g_variant_get_boolean (state)));
    g_object_unref(state);    
}

//win.fullscreen change cb
static void window_fullscreen_changed(GSimpleAction *action, GVariant *state, gpointer user_data)
{
    if(g_variant_get_boolean(state))
        gtk_window_fullscreen(user_data);
    else
        gtk_window_unfullscreen(user_data);

    //chain up
    g_simple_action_set_state(action, state);   
}

//win.busy change cb
static void window_busy_changed(GSimpleAction *action, GVariant *state, gpointer user_data)
{
    GtkWindow *window = (GtkWindow*)user_data;
    GApplication *application = G_APPLICATION (gtk_window_get_application (window));

    /* do this twice to test multiple busy counter increases */
    if (g_variant_get_boolean (state))
    {
        g_application_mark_busy (application);
        g_application_mark_busy (application);
    }
    else
    {
        g_application_unmark_busy (application);
        g_application_unmark_busy (application);
    }

    //chain up
    g_simple_action_set_state (action, state);
}

//win.justify
static void radio_activated(GSimpleAction *action, GVariant *param, gpointer user_data)
{
    g_print("radio_activated()....\n");
    g_action_change_state (G_ACTION (action), param);
}

//win.justify change cb
static void window_justify_changed(GSimpleAction *action, GVariant *state, gpointer user_data)
{
    GtkTextView *text = g_object_get_data (user_data, "bloatpad-text");
    const gchar *str;

    str = g_variant_get_string (state, NULL);

    if (g_str_equal (str, "left"))
        gtk_text_view_set_justification (text, GTK_JUSTIFY_LEFT);
    else if (g_str_equal (str, "center"))
        gtk_text_view_set_justification (text, GTK_JUSTIFY_CENTER);
    else if (g_str_equal (str, "right"))
        gtk_text_view_set_justification (text, GTK_JUSTIFY_RIGHT);
    else
        /* ignore this attempted change */
        return;

    //chain up
    g_simple_action_set_state (action, state);
}

//win.clear 
static void window_clear_activated(GSimpleAction *action, GVariant *param, gpointer user_data)
{
    GtkWindow *window = (GtkWindow*)user_data;
    GtkTextView* text = (GtkTextView*)g_object_get_data(G_OBJECT(window), "bloatpad-text");

    gtk_text_buffer_set_text(gtk_text_view_get_buffer(text), "", -1);
}

static GActionEntry winEntries[] = {
    {"copy", window_copy_activated, NULL, NULL, NULL},
    {"paste", window_paste_activated, NULL, NULL, NULL},
    {"fullscreen", toggle_activated, NULL, "false", window_fullscreen_changed},
    {"busy", toggle_activated, NULL, "false", window_busy_changed},
    {"justify", radio_activated, "s", "'left'", window_justify_changed},        //注意单引号
    {"clear", window_clear_activated, NULL, NULL, NULL}
};

static void new_window(GApplication *app, GFile* file)
{
    GtkWidget *window;
    GtkWidget *grid;
    GtkWidget *scrolled;
    GtkWidget *view;
    GtkWidget *toolbar;
    GtkToolItem *toolbutton;

    window = gtk_application_window_new(GTK_APPLICATION(app));
    gtk_window_set_default_size(GTK_WINDOW(window), 640, 480);
    g_action_map_add_action_entries(G_ACTION_MAP(window), winEntries, G_N_ELEMENTS(winEntries), window);
    gtk_window_set_title(GTK_WINDOW(window), "BloatPad");

    grid = gtk_grid_new();
    gtk_container_add(GTK_CONTAINER(window), grid);

    //toolbar
    toolbar = gtk_toolbar_new();

    toolbutton = gtk_toggle_tool_button_new();
    gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(toolbutton), "format-justify-left");
    gtk_actionable_set_detailed_action_name(GTK_ACTIONABLE(toolbutton), "win.justify::left");
    gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(toolbutton));

    toolbutton = gtk_toggle_tool_button_new();
    gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(toolbutton), "format-justify-center");
    gtk_actionable_set_detailed_action_name(GTK_ACTIONABLE(toolbutton), "win.justify::center");
    gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(toolbutton));

    toolbutton = gtk_toggle_tool_button_new();
    gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(toolbutton), "format-justify-right");
    gtk_actionable_set_detailed_action_name(GTK_ACTIONABLE(toolbutton), "win.justify::right");
    gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(toolbutton));

    toolbutton = gtk_separator_tool_item_new();
    gtk_separator_tool_item_set_draw(GTK_SEPARATOR_TOOL_ITEM(toolbutton), FALSE);
    gtk_tool_item_set_expand(GTK_TOOL_ITEM(toolbutton), TRUE);
    gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(toolbutton));

    toolbutton = gtk_tool_item_new();

    GtkWidget *box, *sw, *label;

    box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
    label = gtk_label_new("Fullscreen:");
    gtk_container_add(GTK_CONTAINER(box), label);
    sw = gtk_switch_new();
    gtk_widget_set_valign(sw, GTK_ALIGN_CENTER);
    gtk_actionable_set_action_name(GTK_ACTIONABLE(sw), "win.fullscreen");
    gtk_container_add(GTK_CONTAINER(box), sw);
    gtk_container_add(GTK_CONTAINER(toolbutton), box);
    gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(toolbutton));

    gtk_grid_attach(GTK_GRID(grid), toolbar, 0, 0, 1, 1);

    //scrolled window and textView
    scrolled = gtk_scrolled_window_new(NULL, NULL);
    gtk_widget_set_hexpand(scrolled, TRUE);
    gtk_widget_set_vexpand(scrolled, TRUE);

    view = gtk_text_view_new();
    g_object_set_data((GObject*)window, "bloatpad-text", view);      //store for use afterwards
    gtk_container_add(GTK_CONTAINER(scrolled), view);

    gtk_grid_attach(GTK_GRID(grid), scrolled, 0,1,1,1);

    if(file != NULL)
    {
        gchar *contents;
        gsize len;
        if(g_file_load_contents(file, NULL, &contents, &len, NULL, NULL))
        {
            GtkTextBuffer *buffer;
            buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(view));
            gtk_text_buffer_set_text(buffer, contents, len);
            g_free(contents);
        }
    }

    g_signal_connect(gtk_text_view_get_buffer(GTK_TEXT_VIEW(view))
                        , "changed",
                        G_CALLBACK(text_buffer_changed_cb)
                        , window);
    text_buffer_changed_cb(gtk_text_view_get_buffer(GTK_TEXT_VIEW(view)), window);     

    gtk_widget_show_all(GTK_WIDGET(window));
}

static void dump_accels(GtkApplication *app)
{
    gchar** actions;
    gint i;
    actions = gtk_application_list_action_descriptions(app);
    for(i = 0; actions[i]; i++)
    {
        gchar** accels;
        gchar* str;

        accels = gtk_application_get_accels_for_action(app, actions[i]);
        str = g_strjoinv(",", accels);
        g_print("%s -> %s\n", actions[i], str);
        g_strfreev(accels);
        g_free(str);
    }
    g_strfreev(actions);
}

//app.new
static void new_activated(GSimpleAction *action, GVariant *param, gpointer user_data)
{
    GApplication *app = (GApplication*) user_data;
    g_application_activate(app);
}

//app.about
static void about_activated(GSimpleAction *action, GVariant *param, gpointer user_data)
{
  gtk_show_about_dialog (NULL,
                         "program-name", "Bloatpad",
                         "title", "About Bloatpad",
                         "comments", "dervied from the gtk example, Bloatpad.",
                         NULL);
}

//app.quit
static void quit_activated(GSimpleAction *action, GVariant *param, gpointer user_data)
{
    GApplication *app = user_data;

    g_application_quit (app);
}

static void combo_changed (GtkComboBox *combo, gpointer user_data)
{
    GtkEntry *entry = g_object_get_data (user_data, "entry");
    const gchar *action;
    gchar **accels;
    gchar *str;

    action = gtk_combo_box_get_active_id (combo);

    if (!action)
        return;

    accels = gtk_application_get_accels_for_action (gtk_window_get_application (user_data), action);
    str = g_strjoinv (",", accels);
    g_strfreev (accels);

    gtk_entry_set_text (entry, str);
}

static void response (GtkDialog *dialog, guint response_id, gpointer user_data)
{
    GtkEntry *entry = g_object_get_data (user_data, "entry");
    GtkComboBox *combo = g_object_get_data (user_data, "combo");
    const gchar *action;
    const gchar *str;
    gchar **accels;

    if (response_id == GTK_RESPONSE_CLOSE)
    {
        gtk_widget_destroy (GTK_WIDGET (dialog));
        return;
    }

    action = gtk_combo_box_get_active_id (combo);

    if (!action)
        return;

    str = gtk_entry_get_text (entry);
    accels = g_strsplit (str, ",", 0);

    gtk_application_set_accels_for_action (gtk_window_get_application (user_data), action, (const gchar **) accels);
    g_strfreev (accels);
}

//app.edit-accels
static void edit_accels_activated(GSimpleAction *action, GVariant *param, gpointer user_data)
{
    GtkApplication *app = user_data;
    GtkWidget *combo;
    GtkWidget *entry;
    gchar **actions;
    GtkWidget *dialog;
    gint i;

    dialog = gtk_dialog_new();
    gtk_window_set_application(GTK_WINDOW(dialog), app);
    actions = gtk_application_list_action_descriptions(app);
    combo = gtk_combo_box_text_new();
    gtk_container_add(GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), combo);
    for(i = 0; actions[i]; i++)
    {
        g_print("%s \n", actions[i]);
        gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(combo), actions[i], actions[i]);
    }
    g_signal_connect(combo, "changed", G_CALLBACK(combo_changed), dialog);
    entry = gtk_entry_new();
    gtk_container_add(GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), entry);
    gtk_dialog_add_button(GTK_DIALOG(dialog), "Close", GTK_RESPONSE_CLOSE);
    gtk_dialog_add_button(GTK_DIALOG(dialog), "Set", GTK_RESPONSE_APPLY);
    g_signal_connect(dialog, "response", G_CALLBACK(response), dialog);
    g_object_set_data(G_OBJECT(dialog), "combo", combo);
    g_object_set_data(G_OBJECT(dialog), "entry", entry);

    gtk_widget_show_all(dialog);
}

static gboolean update_time (gpointer user_data)
{
    BloatPad *bloatpad = user_data;
    GDateTime *now;
    gchar *time;

    while (g_menu_model_get_n_items (G_MENU_MODEL (bloatpad->m_timeMenu)))
        g_menu_remove (bloatpad->m_timeMenu, 0);

    g_message ("Updating the time menu (which should be open now)...");

    now = g_date_time_new_now_local ();
    time = g_date_time_format (now, "%T");
    g_menu_append (bloatpad->m_timeMenu, time, NULL);
    g_date_time_unref (now);
    g_free (time);

    return G_SOURCE_CONTINUE;
}

//app.time-active
static void time_active_changed(GSimpleAction *action, GVariant *state, gpointer user_data)
{
    BloatPad *bp = user_data;
    if(g_variant_get_boolean(state))
    {
        if(!bp->m_timeout)
        {
            bp->m_timeout = g_timeout_add(1000, update_time, bp);
            update_time(bp);
        }
    }
    else
    {
        if(bp->m_timeout)
        {
            g_source_remove(bp->m_timeout);
            bp->m_timeout = 0;
        }
    }

    //chain up
    g_simple_action_set_state(action, state);
}

//app.clear-all
static void clear_all_activated(GSimpleAction *action, GVariant *param, gpointer user_data)
{
    GtkApplication *app = GTK_APPLICATION (user_data);
    GList *iter;

    for (iter = gtk_application_get_windows (app); iter; iter = iter->next)
        g_action_group_activate_action (iter->data, "clear", NULL);
}

static GActionEntry appEntries[] = {
    {"new", new_activated, NULL, NULL, NULL},
    {"about", about_activated, NULL, NULL, NULL},
    {"quit", quit_activated, NULL, NULL, NULL},
    {"edit-accels", edit_accels_activated},
    {"time-active", NULL, NULL, "false", time_active_changed},
    {"clear-all", clear_all_activated},
};

static void bloat_pad_startup(GApplication* app)
{
    BloatPad *bp = (BloatPad*) app;
    GtkApplication *gtkApp = GTK_APPLICATION(app);
    struct {
        const gchar* action_and_target;
        const gchar* accelerator[2]; 
    } accels[]= {
        {"app.new",{"<Control>n",NULL}},
        {"app.quit",{"<Control>q",NULL}},
        {"win.copy",{"<Control>c",NULL}},
        {"win.paste",{"<Control>p",NULL}},
        {"win.justify::left",{"<Control>l",NULL}},
        {"win.justify::center",{"<Control>m",NULL}},
        {"win.justify::right",{"<Control>r",NULL}}
    };

    const gchar* new_accels[]={"<Control>n", "<Control>t", NULL};
    gint i;

    //chain up
    G_APPLICATION_CLASS(bloat_pad_parent_class)->startup(app);

    //set actions
    g_action_map_add_action_entries(G_ACTION_MAP(app), appEntries, G_N_ELEMENTS(appEntries), app);

    //set accels
    for(i = 0; i< G_N_ELEMENTS(accels); i++)
    {
        gtk_application_set_accels_for_action(gtkApp, accels[i].action_and_target, accels[i].accelerator);
    }
    gtk_application_set_accels_for_action(gtkApp, "app.new", new_accels);

    dump_accels(gtkApp);


    //build icon-menu
    GMenu* menu= gtk_application_get_menu_by_id(gtkApp, "icon-menu");
    GMenuItem *menuitem;
    GFile *file;
    GBytes *bytes;
    GIcon *icon;
    GIcon *icon2;
    GEmblem *emblem;

    //file icon from libgtk resource 
    file = g_file_new_for_uri("resource:///org/gtk/libgtk/icons/16x16/actions/gtk-select-color.png");
    icon = g_file_icon_new(file);
    menuitem = g_menu_item_new("File Icon", NULL);
    g_menu_item_set_icon(menuitem, icon);
    g_menu_append_item(menu, menuitem);
    g_object_unref(menuitem);
    g_object_unref(file);
    g_object_unref(icon);

    //themed icon
    icon = g_themed_icon_new("edit-find");
    menuitem = g_menu_item_new("Themed Icon", NULL);
    g_menu_item_set_icon(menuitem, icon);
    g_menu_append_item(menu, menuitem);
    g_object_unref(menuitem);
    g_object_unref(icon);

    //bytes icon
    bytes = g_resources_lookup_data("/org/gtk/libgtk/icons/16x16/actions/gtk-select-font.png", 0, NULL);
    icon = g_bytes_icon_new(bytes);
    menuitem = g_menu_item_new("Bytes Icon", NULL);
    g_menu_item_set_icon(menuitem, icon);
    g_menu_append_item(menu, menuitem);
    g_object_unref(menuitem);
    g_object_unref(icon);
    g_bytes_unref(bytes);       //注意， 不是g_object_unref

    //pixbuf icon
    icon = G_ICON (gdk_pixbuf_new_from_resource ("/org/gtk/libgtk/icons/16x16/actions/gtk-preferences.png", NULL));
    menuitem = g_menu_item_new ("Pixbuf Icon", NULL);
    g_menu_item_set_icon (menuitem, icon);
    g_menu_append_item (menu, menuitem);
    g_object_unref (menuitem);
    g_object_unref (icon);

    //emblemed icon
    file = g_file_new_for_uri ("resource:///org/gtk/libgtk/icons/16x16/actions/gtk-page-setup.png");
    icon = g_file_icon_new (file);
    emblem = g_emblem_new (icon);
    g_object_unref (icon);
    g_object_unref (file);

    file = g_file_new_for_uri ("resource:///org/gtk/libgtk/icons/16x16/actions/gtk-orientation-reverse-portrait.png");
    icon2 = g_file_icon_new (file);
    icon = g_emblemed_icon_new (icon2, emblem);     //icon缩小放置在icon2上合成新icon
    menuitem = g_menu_item_new ("Emblemed Icon", NULL);
    g_menu_item_set_icon (menuitem, icon);
    g_menu_append_item (menu, menuitem);
    g_object_unref (menuitem);
    g_object_unref (icon);
    g_object_unref (icon2);
    g_object_unref (file);
    g_object_unref (emblem);

    //themed icon
    icon = g_themed_icon_new("weather-severe-alert-symbolic");
    menuitem = g_menu_item_new("Themed Icon", NULL);
    g_menu_item_set_icon(menuitem, icon);
    g_menu_append_item(menu, menuitem);
    g_object_unref(menuitem);
    g_object_unref(icon);

    bp->m_timeMenu = gtk_application_get_menu_by_id(gtkApp, "time-menu");

}

static void bloat_pad_shutdown(GApplication* app)
{
    BloatPad *bp = (BloatPad*) app;
    
    //destroy the time source
    if(bp->m_timeout)
    {
        g_source_remove(bp->m_timeout);
        bp->m_timeout = 0;
    }

    //chain up
    G_APPLICATION_CLASS(bloat_pad_parent_class)->shutdown(app);

}

static void bloat_pad_activate(GApplication* app)
{
    new_window(app, NULL);
}

static void bloat_pad_open(GApplication *app, GFile **files, gint n_files, const gchar *hint)
{
    gint i;
    for(i = 0; i< n_files; i++)
    {
        new_window(app, files[i]);
    }
}

static void bloat_pad_finalize(GObject* obj)
{
    //chain up
    G_OBJECT_CLASS(bloat_pad_parent_class)->finalize(obj);      
}

static void bloat_pad_class_init(BloatPadClass* clazz)
{
    GApplicationClass *appCls = G_APPLICATION_CLASS(clazz);
    GObjectClass* objCls = G_OBJECT_CLASS(clazz);

    appCls->startup = bloat_pad_startup;
    appCls->shutdown = bloat_pad_shutdown;
    appCls->activate = bloat_pad_activate;
    appCls->open = bloat_pad_open;

    objCls->finalize = bloat_pad_finalize;

}

static void bloat_pad_init(BloatPad* app)
{
    //pass
}

BloatPad* bloat_pad_new(void)
{
    BloatPad *app;
    g_set_application_name("Bloatpad");
    app = g_object_new(bloat_pad_get_type(),
                        "application-id", "com.gitee.zcatt.gnome.example.a02_bloatpad",
                        "flags", G_APPLICATION_HANDLES_OPEN,
                        "inactivity-timeout", 30000,
                        "register-session", TRUE,
                        NULL );
    return app;
}

int main(int argc, char** argv)
{
    BloatPad *bp;
    int status;
    const char* accels[] = {"F11", NULL};

    bp = bloat_pad_new();

    gtk_application_set_accels_for_action(GTK_APPLICATION(bp), "win.fullscreen", accels);

    status = g_application_run(G_APPLICATION(bp), argc, argv);
    
    g_object_unref(bp);

    return status;
}